package com.px.lovepath.datebase;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.text.TextUtils;


public class ManagedDatabase {
	private final ManagedDatabaseInfo mDbInfo;
	private SQLiteDatabase mDb = null;
	private Thread mBkThread = null;
	
	// ### 构造函数 ###
	protected ManagedDatabase(ManagedDatabaseInfo dbInfo) {
		assert dbInfo != null;
		assert TextUtils.isEmpty(dbInfo.mDbUri) == false;
		
		mDbInfo = new ManagedDatabaseInfo(dbInfo);
	}
	
	// ### 属性 ###
	public synchronized String getDatabaseUri() {
		return mDbInfo.mDbUri;
	}
	public synchronized String getBackupUri() {
		return mDbInfo.mBkUri;
	}
	
	// ### 方法 ###
	public synchronized void beginTransaction() {
		open();
		mDb.beginTransaction();
	}
	public synchronized SQLiteStatement compileStatement(String sql) {
		open();
		return mDb.compileStatement(sql);
	}
	public synchronized int delete(String table, String whereClause, String[] whereArgs) {
		open();
		return mDb.delete(table, whereClause, whereArgs);
	}
	public synchronized void endTransaction() {
		open();
		mDb.endTransaction();
	}
	public synchronized void execSQL(String sql, Object[] bindArgs) throws SQLException {
		open();
		mDb.execSQL(sql, bindArgs);
	}
	public synchronized void execSQL(String sql) throws SQLException {
		open();
		mDb.execSQL(sql);
	}
	public synchronized int getVersion() {
		open();
		return mDb.getVersion();
	}
	public synchronized boolean inTransaction() {
		open();
		return mDb.inTransaction();
	}
	public synchronized long insert(String table, String nullColumnHack, ContentValues values) {
		open();
		return mDb.insert(table, nullColumnHack, values);
	}
	public synchronized long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException {
		open();
		return mDb.insertOrThrow(table, nullColumnHack, values);
	}
	public synchronized long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) {
		open();
		return mDb.insertWithOnConflict(table, nullColumnHack, initialValues,	conflictAlgorithm);
	}
	public synchronized boolean isReadOnly() {
		open();
		return mDb.isReadOnly();
	}
	public synchronized Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
		open();
		return mDb.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
	}
	public synchronized Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
		open();
		return mDb.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
	}
	public synchronized Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
		open();
		return mDb.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
	}
	public synchronized Cursor rawQuery(String sql, String[] selectionArgs) {
		open();
		return mDb.rawQuery(sql, selectionArgs);
	}
	public synchronized long replace(String table, String nullColumnHack, ContentValues initialValues) {
		open();
		return mDb.replace(table, nullColumnHack, initialValues);
	}
	public synchronized long replaceOrThrow(String table, String nullColumnHack,	ContentValues initialValues) throws SQLException {
		open();
		return mDb.replaceOrThrow(table, nullColumnHack, initialValues);
	}
	public synchronized void setTransactionSuccessful() {
		open();
		mDb.setTransactionSuccessful();
	}
	public synchronized void setVersion(int version) {
		open();
		mDb.setVersion(version);
	}
	public synchronized int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
		open();
		return mDb.update(table, values, whereClause, whereArgs);
	}
	public synchronized int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) {
		open();
		return mDb.updateWithOnConflict(table, values, whereClause, whereArgs, conflictAlgorithm);
	}
	
	// ### 隐藏方法 ###
	protected synchronized void open() {
		if (mDb != null)
			return;
		
		restoreDatabase();
		
		try {
			File dbFile = new File(Uri.parse(mDbInfo.mDbUri).getPath());
			mDb = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
		} catch (Exception e) {
			mDb = SQLiteDatabase.create(null);
		}
		
		assert mDb != null;
	}
	protected synchronized void close() {
		if (mDb == null)
			return;
		
		mDb.close();
		mDb = null;
		
		backupDatabase();
	}
	
	// ### 实现方法 ###
	protected void backupDatabase() {
		if (TextUtils.isEmpty(mDbInfo.mBkUri))
			return;

		final File dbFile = new File(mDbInfo.mDbUri);
		final File bkFile = new File(mDbInfo.mBkUri);

		if (bkFile.length() == dbFile.length() && bkFile.lastModified() > dbFile.lastModified())
			return;

		if (mBkThread != null)
			return;

		mBkThread = new Thread(new Runnable() {
			@Override
			public void run() {
				doBackupDatabase(dbFile, bkFile);
				mBkThread = null;
			}
		});
		mBkThread.start();
	}
	protected synchronized void doBackupDatabase(File dbFile, File bkFile) {
		if (mDb != null)
			return;

		File tmpFile = new File(mDbInfo.mBkUri + ".tmp");
		try {
			FileChannel srcChannel = new FileInputStream(dbFile).getChannel();
			FileChannel dstChannel = new FileOutputStream(tmpFile).getChannel();
			dstChannel.position(0);
			dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
			dstChannel.truncate(srcChannel.size());
			srcChannel.close();
			dstChannel.close();
				
			bkFile.delete();
			tmpFile.renameTo(bkFile);
		} catch (Exception e) {
			e.printStackTrace();
			tmpFile.delete();
		}
	}
	protected void restoreDatabase() {
		assert mDb == null;
		
		if (TextUtils.isEmpty(mDbInfo.mBkUri))
			return;

		File dbFile = new File(mDbInfo.mDbUri);
		File bkFile = new File(mDbInfo.mBkUri);
		
		if (dbFile.exists() == false && bkFile.exists()) {
			File tmpFile = new File(mDbInfo.mDbUri + ".tmp");
			try {
				FileChannel srcChannel = new FileInputStream(bkFile).getChannel();
				FileChannel dstChannel = new FileOutputStream(tmpFile).getChannel();
				dstChannel.position(0);
				dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
				dstChannel.truncate(srcChannel.size());
				srcChannel.close();
				dstChannel.close();

				tmpFile.renameTo(dbFile);
			} catch (Exception e) {
				e.printStackTrace();
				tmpFile.delete();
			}
		}
	}
}
